/**
 * @file date-range
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  PureComponent,
  PropTypes
} from 'react';
import * as cx from 'classnames';
import {findDOMNode} from 'react-dom';
import {Editor, EditorState, RichUtils, Modifier, Entity, CompositeDecorator} from 'draft-js';
import decorator from './decorators/index.jsx';
import * as isSoftNewlineEvent from 'draft-js/lib/isSoftNewlineEvent';
import {
  changeBlockType,
  changeBlockDepth,
  insertBlockAfter
} from './util';
import {
  BLOCK_TYPE,
  customStyleMap
} from './constants';
import Toolbar from './toolbar';
import 'draft-js/dist/Draft.css';
import '../rich-text.scss';
import {stateToHTML} from 'draft-js-export-html';
import {stateFromHTML} from 'draft-js-import-html';
import * as debounce from 'lodash/debounce';


function getBlockStyle(block) {
  switch (block.getType()) {
    case BLOCK_TYPE.BLOCKQUOTE: return 'blockquote';
    case BLOCK_TYPE.UNSTYLED: return 'paragraph';
    default: return null;
  }
}

function isListItem(block) {
  let blockType = block.getType();
  return (
    blockType === BLOCK_TYPE.UNORDERED_LIST_ITEM ||
    blockType === BLOCK_TYPE.ORDERED_LIST_ITEM
  );
}

export default class RichEditor extends PureComponent
{
  static propTypes = {
    className: PropTypes.string,
    onChange: PropTypes.func.isRequired
  };

  static defaultProps = {
    onChange: value => console.log(value),
    placeholder: '请输入'
  };

  constructor(props) {
    super(props);

    this.state = {
      editorState: props.value
        ? EditorState.createWithContent(stateFromHTML(props.value), decorator)
        : EditorState.createEmpty(decorator),
      focus: false
    };

    this.syncToHtml = debounce(this.syncToHtml, 300, {
      leading: true,
      trailing: false
    });

    this.handleFocus = this.handleFocus.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleTab = this.handleTab.bind(this);
    this.handleKeyCommand = this.handleKeyCommand.bind(this);
    this.handleReturn = this.handleReturn.bind(this);
    this.toggleInlineStyle = this.toggleInlineStyle.bind(this);
    this.toggleBlockStyle = this.toggleBlockStyle.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;

    if (props.value !== nextProps.value) {
      EditorState.push(
        this.state.editorState,
        stateFromHTML(nextProps.value),
        'secondary-paste'
      );
    }
  }

  handleChange(editorState) {
    this.setState({editorState}, this.syncToHtml);
  }

  handleKeyCommand(command) {
    const {editorState} = this.state;
    const newState = RichUtils.handleKeyCommand(editorState, command);
    if (newState) {
      this.handleChange(newState);
      return true;
    }
    return false;
  }

  handleTab(e) {
    const maxDepth = 4;
    this.handleChange(RichUtils.onTab(e, this.state.editorState, maxDepth));
  }

  handleFocus() {
    this.setState({
      focus: true
    });

    this.refs.editor.focus();
  }

  handleBlur() {
    this.setState({
      focus: false
    });
  }

  handleReturn(event) {
    if (this.handleReturnSoftNewline(event)) {
      return true;
    }else if (this.handleReturnEmptyListItem()) {
      return true;
    } else if (this.handleReturnSpecialBlock()) {
      return true;
    }
    return false;
  }

  // `shift + return` should insert a soft newline.
  handleReturnSoftNewline(event) {
    let editorState = this.state.editorState;

    if (isSoftNewlineEvent(event)) {
      let selection = editorState.getSelection();
      if (selection.isCollapsed()) {
        this.handleChange(RichUtils.insertSoftNewline(editorState));
      } else {
        let content = editorState.getCurrentContent();
        let newContent = Modifier.removeRange(content, selection, 'forward');
        let newSelection = newContent.getSelectionAfter();
        let block = newContent.getBlockForKey(newSelection.getStartKey());
        newContent = Modifier.insertText(
          newContent,
          newSelection,
          '\n',
          block.getInlineStyleAt(newSelection.getStartOffset()),
          null,
        );
        this.handleChange(
          EditorState.push(editorState, newContent, 'insert-fragment')
        );
      }
      return true;
    }
    return false;
  }

  // If the cursor is in an empty list item when return is pressed, then the
  // block type should change to normal (end the list).
  handleReturnEmptyListItem() {
    let editorState = this.state.editorState;

    let selection = editorState.getSelection();
    if (selection.isCollapsed()) {
      let contentState = editorState.getCurrentContent();
      let blockKey = selection.getStartKey();
      let block = contentState.getBlockForKey(blockKey);
      if (isListItem(block) && block.getLength() === 0) {
        let depth = block.getDepth();
        let newState = (depth === 0) ?
          changeBlockType(editorState, blockKey, BLOCK_TYPE.UNSTYLED) :
          changeBlockDepth(editorState, blockKey, depth - 1);
        this.handleChange(newState);
        return true;
      }
    }
    return false;
  }

  // If the cursor is at the end of a special block (any block type other than
  // normal or list item) when return is pressed, new block should be normal.
  handleReturnSpecialBlock() {
    let editorState = this.state.editorState;

    let selection = editorState.getSelection();
    if (selection.isCollapsed()) {
      let contentState = editorState.getCurrentContent();
      let blockKey = selection.getStartKey();
      let block = contentState.getBlockForKey(blockKey);
      if (!isListItem(block) && block.getType() !== BLOCK_TYPE.UNSTYLED) {
        // If cursor is at end.
        if (block.getLength() === selection.getStartOffset()) {
          let newEditorState = insertBlockAfter(
            editorState,
            blockKey,
            BLOCK_TYPE.UNSTYLED
          );
          this.handleChange(newEditorState);
          return true;
        }
      }
    }
    return false;
  }

  toggleBlockStyle(blockType) {
    this.handleChange(
      RichUtils.toggleBlockType(
        this.state.editorState,
        blockType
      )
    );
  }

  toggleInlineStyle(inlineStyle) {
    this.handleChange(
      RichUtils.toggleInlineStyle(
        this.state.editorState,
        inlineStyle
      )
    );
  }

  syncToHtml() {
    const editorState = this.state.editorState;
    const onChange = this.props.onChange;

    onChange(stateToHTML(editorState.getCurrentContent()));
  }

  render() {
    const {
      className,
      placeholder,
      reciever
    } = this.props;

    const {
      editorState,
      focus
    } = this.state;

    const content = editorState.getCurrentContent();
    const showPlaceHolder = !content.hasText() && content.getBlockMap().first().getType() === 'unstyled';


    return (
      <div className={cx('rich-text-editor', className)}>
        <Toolbar
          editorState={editorState}
          toggleBlockStyle={this.toggleBlockStyle}
          toggleInlineStyle={this.toggleInlineStyle}
          onChange={this.handleChange}
          focusEditor={this.handleFocus}
          reciever={reciever}
        />

        <div
          className={cx('rich-text-editor-inner form-control', {
            focus
          })}
          onClick={this.handleFocus}
        >
          <Editor
            editorState={editorState}
            blockStyleFn={getBlockStyle}
            customStyleMap={customStyleMap}
            handleKeyCommand={this.handleKeyCommand}
            onChange={this.handleChange}
            onTab={this.handleTab}
            onFocus={this.handleFocus}
            onBlur={this.handleBlur}
            handleReturn={this.handleReturn}
            placeholder={showPlaceHolder && placeholder}
            ref="editor"
            spellCheck={true}
          />
        </div>
      </div>
    );
  }
}
